/*
* This file is licenses under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License.  You may obtain a copy of the License at
*
*    http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef DOUBLEBUFFER_H_
#define DOUBLEBUFFER_H_

typedef enum {
	dbuf_wr =0,
	dbuf_rd = 1
}dbuf_op;

template <class T>
class doubleBuffer {
public:

	doubleBuffer() {

		rd_now = true;
		front_buf_sz = 0;
		front_ptr[0] = front_ptr[1] = back_ptr[0] = back_ptr[1] = 0;
		_capacity = 0;
	}

	doubleBuffer(int elemCnt) {

		rd_now = true;
		_capacity = 0;
		front_buf_sz = 0;
		front_ptr[0] = front_ptr[1] = back_ptr[0] = back_ptr[1] = 0;
		set_capacity(elemCnt);
	}

	doubleBuffer(int elemCnt, int front_buffer_size) {

		rd_now = true;
		_capacity = 0;
		set_capacity(elemCnt, front_buffer_size);
	}

	virtual ~doubleBuffer() {

	/*	if (_capacity > 0) {

			delete (unsigned char*)buffer[0];
			delete (unsigned char*)buffer[1];
		}*/
	}

	bool set_capacity(size_t elemCnt, int front_buffer_size = 0)
	{
		if (_capacity > 0)
			return false; // no resizing
		front_buf_sz = front_buffer_size;
		front_ptr[0] = front_ptr[1] = back_ptr[0] = back_ptr[1] = front_buf_sz;
		// make sure they are in right order
		unsigned char* p = (unsigned char*)new char[sizeof(T)*elemCnt];
		memset(p, 0, sizeof(T)*elemCnt);
		buffer[0] = (T*)p;
		p = (unsigned char*)new char[sizeof(T)*elemCnt];
		memset(p, 0, sizeof(T)*elemCnt);
		buffer[1] = (T*)p;
		_capacity = (size_t)elemCnt;
		return true;
	}

	bool empty(dbuf_op op = dbuf_rd) {

		if (back_ptr[b_id(op)] == front_buf_sz) // never being filled
			return true;
		if (front_ptr[b_id(op)] == back_ptr[b_id(op)]) // read all
			return true;
		return false;
	}

	bool full(dbuf_op op = dbuf_wr) {

		int n = back_ptr[b_id(op)];
		return n >= (int)_capacity;
	}

	T& front()
	{
		if (empty())
			throw "buffer is empty";
		T* p = rd_at(front_ptr[b_id(dbuf_rd)]);
		return *p;
	}

	void pop_front() {

		if (!empty())
			++front_ptr[b_id(dbuf_rd)];
	}


	bool push_back(T& element)
	{
		if (full(dbuf_wr))
			return false;
		T* p = wr_at(back_ptr[b_id(dbuf_wr)]++);
		memcpy(p, &element, sizeof(T));
		return true;
	}

	bool push_front(T& element)
	{
		if (front_ptr[b_id(dbuf_rd)] <= 0)
			return false;
		T* p = rd_at(--front_ptr[b_id(dbuf_rd)]);
		memcpy(p, &element, sizeof(T));
		return true;
	}


	size_t size(dbuf_op op) {

		int n = (int)(back_ptr[b_id(op)] - front_ptr[b_id(op)]);
		return n;
	}

	size_t capacity() {return _capacity;}
	size_t allowedFw() {return front_buf_sz;}

	void flip() {

		rd_now = !rd_now;
		T* p = wr_at(0);
		memset(p, 0, sizeof(T)*_capacity);
		front_ptr[b_id(dbuf_wr)] = back_ptr[b_id(dbuf_wr)] = front_buf_sz;
	}
private:

	int b_id(dbuf_op op) {

		if (op == dbuf_wr)
			return !rd_now;
		else
			return rd_now;
	}

	T* rd_at(int pos) {
		T* p = buffer[rd_now];
		return &p[pos];
	}

	T* wr_at(int pos) {
		T* p = buffer[!rd_now];
		return &p[pos];
	}
	size_t _capacity;
	T* buffer[2];
	int front_ptr[2]; /// 0 for empty buffer; shows position of "oldest" element still in a queue
	int back_ptr[2]; /// 0 for empty buffer; shows position of last element added
	bool rd_now;
	int front_buf_sz; /// "lazy" implementation of push_front() function: we just always leave some buffer at the
					  /// beginning and never use it except "push_front()"

};


#endif /* DOUBLEBUFFER_H_ */
